﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.ServiceModel;
using System.Text;
using ICSharpCode.SharpZipLib.Core;
using ICSharpCode.SharpZipLib.Zip;
using nwrole.lib;
using nwrole.toolbox.sync.contract;
using BLToolkit.Data;
using BLToolkit.Data.DataProvider;
using System.Data.SqlClient;
using System.Data;
using nwrole.toolbox.sync.entity;

namespace nwrole.toolbox.sync.service
{
    public class SyncCommandEnvelopeWrapper {

        public enum CTE
        {
            Scalar, NonQuery, Reader, Net, Xml
        }

        public CTE CT
        {
            get
            {
                if (baseCommand.MessageType == "s1")
                {
                    return CTE.Scalar;
                }
                if (baseCommand.MessageType == "s2")
                {
                    return CTE.NonQuery;
                }
                if (baseCommand.MessageType == "s3")
                {
                    return CTE.Reader;
                }
                if (baseCommand.MessageType == "s4")
                {
                    return CTE.Net;
                }
                if (baseCommand.MessageType == "s5")
                {
                    return CTE.Xml;
                }
                return CTE.Scalar;
            }

        }

        public SyncCommandEnvelope baseCommand = null;
        public SyncCommandEnvelopeWrapper(SyncCommandEnvelope cmd)
        {
            baseCommand = cmd;
        }
    }

    public class SyncDestServiceParams
    {
        public string DestDbConnectionString { get; set; }
        public DataProviderType DataProviderType { get; set; }

        public SyncDestServiceParams() {}

        public SyncDestServiceParams(string destDbConnectionStr, DataProviderType dataProviderType)
        {
            DestDbConnectionString = destDbConnectionStr;
            DataProviderType = dataProviderType;
        }
    }

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Single, IncludeExceptionDetailInFaults = true)]
    public class SyncDestinationService : ISyncDestinationService, IParamService<SyncDestServiceParams>
    {
        private string destinationConnectionString;

        private DataProviderType dataProviderType;

        private static Dictionary<string, SyncChunkFile> chunkFiles = new Dictionary<string, SyncChunkFile>();
        
        public void InitParamService(SyncDestServiceParams serviceParams)
        {
            destinationConnectionString = serviceParams.DestDbConnectionString;
            dataProviderType = serviceParams.DataProviderType;
        }

        private DataProviderBase CreateDataProvider()
        {
            switch (dataProviderType)
            {
                case DataProviderType.Sql:
                    return new SqlDataProvider();
                default:
                    return null;
            }
        }

        public SyncMessageEnvelope ProcessMessage(SyncCommandEnvelope c)
        {
            SyncCommandEnvelopeWrapper wrapper = new SyncCommandEnvelopeWrapper(c);
            SyncMessageEnvelope result = null;

            if (wrapper.CT == SyncCommandEnvelopeWrapper.CTE.Scalar)
            {
                result = ProcessMessageSqlExecuteScalar(wrapper);
            }
            else if (wrapper.CT == SyncCommandEnvelopeWrapper.CTE.NonQuery)
            {
                result = ProcessMessageSqlNonQuery(wrapper);
            }
            else if (wrapper.CT == SyncCommandEnvelopeWrapper.CTE.Reader)
            {
                result = ProcessMessageSqlReader(wrapper);
            }
            else if (wrapper.CT == SyncCommandEnvelopeWrapper.CTE.Xml)
            {
                result = ProcessMessageSqlXmlReader(wrapper);
            }
            else if (wrapper.CT == SyncCommandEnvelopeWrapper.CTE.Net)
            {
                result = ProcessMessageExecuteNet(wrapper);
            }

            return result;
        }

        private SyncMessageEnvelope ProcessMessageSqlXmlReader(SyncCommandEnvelopeWrapper command)
        {
            SyncMessageEnvelope result = new SyncMessageEnvelope();
            string sqlCmd = Encoding.UTF8.GetString(command.baseCommand.Bytes, 0, command.baseCommand.Bytes.Length);
            try
            {
                using (var con = new SqlConnection(destinationConnectionString))
                {
                    con.Open();

                    SqlCommand cmd = con.CreateCommand();
                    cmd.CommandText = sqlCmd;

                    System.Xml.XmlReader reader = cmd.ExecuteXmlReader();
                    if (reader != null)
                    {
                        StringBuilder sb = new StringBuilder();
                        while (reader.Read())
                            sb.AppendLine(reader.ReadOuterXml());
                        result.Bytes = System.Text.Encoding.UTF8.GetBytes(sb.ToString());
                    }
                }
            }
            catch (Exception e)
            {
                Trace.TraceError("SyncDestinationService ProcessMessageSqlXmlReader (sqlText:" + sqlCmd + ") Error: " + e);
            }
            return result;
        }

        public SyncMessageEnvelope SyncComplete(SyncCompleteEnvelope command)
        {
            SyncMessageEnvelope result = null;

            if (!string.IsNullOrEmpty(command.DestinationSyncCompleteSPName))
            {
                lock (LockObject.locker)
                {
                    try
                    {
                        LockObject.Lock();
                        string sp = command.DestinationSyncCompleteSPName;
                        string messageType = null;
                        string clientCode = null;
                        Guid messageGuid = command.MsgCorr;
                        long msgId = RunCommand2(sp, messageType, clientCode, messageGuid);
                    }
                    catch (Exception e)
                    {
                        // PluginLogger.LogException(KällTyp.Plugin, "BackOfficeServiceHostSyncPlugin", e, 0);
                        // SimplePlugin.EventLogger_LogException("BackOfficeInbox", e);
                        Trace.TraceError("SyncDestinationService SyncComplete (SPname:" + command.DestinationSyncCompleteSPName + " type:" + command.MessageType + ") Error: " + e);
                        throw;
                    }
                    finally
                    {
                        LockObject.Unlock();
                    }
                }
            }
            System.Console.WriteLine("\r\ndone:" + DateTime.Now.ToString());
            return result;
        }

        private SyncMessageEnvelope ProcessMessageExecuteNet(SyncCommandEnvelopeWrapper command)
        {
            SyncMessageEnvelope result = null;
            return result;
        }

        private SyncMessageEnvelope ProcessMessageSqlExecuteScalar(SyncCommandEnvelopeWrapper command)
        {
            SyncMessageEnvelope result = null;
            return result;
        }

        private SyncMessageEnvelope ProcessMessageSqlNonQuery(SyncCommandEnvelopeWrapper command)
        {
            SyncMessageEnvelope result = null;
            return result;
        }

        private SyncMessageEnvelope ProcessMessageSqlReader(SyncCommandEnvelopeWrapper command)
        {
            // TODO: This does nothing 120924
            SyncMessageEnvelope result = null;
            DbManager.DefaultConfiguration = "";
            using (DbManager db = new DbManager(CreateDataProvider(), destinationConnectionString))
            {
                //db.SetCommand(dropImportSP).ExecuteScalar.ExecuteNonQuery.e.ExecuteNonQuery();

                //string sql1 = sqlBuilder.CreateImportSP(map);
                //db.SetCommand(sql1).ExecuteNonQuery();

            }
            return result;
        }


        // orginal sync

        #region lockRegion

        public class LockClass
        {

            public Object locker = new Object();

            int lockCounter = 0;
            int instanceCounter = 0;

            public LockClass()
            {
                instanceCounter++;
                if (instanceCounter != 1)
                {
                    throw new Exception("LockClass error");
                }
            }

            public void Lock()
            {
                lockCounter++;
                if (lockCounter != 1)
                {
                    throw new Exception("Lock error");
                }
            }

            public void Unlock()
            {
                lockCounter--;
                if (lockCounter != 0)
                {
                    throw new Exception("Unlock error");
                }
            }
        }

        static readonly LockClass LockObject = new LockClass();

        #endregion

        public void ProcessMessageOld(SyncMessageEnvelope message)
        {
            System.Console.WriteLine("ProcessMessageOld " + message.MessageType + " " + DateTime.Now);

            // RunCommand(commandName, message.MessageData, false, true);

            lock (LockObject.locker)
            {
                try
                {
                    LockObject.Lock();

                    string commandName1 = string.Format("[LoadMessage]", message.MessageType);
                    long msgId = RunCommand2(commandName1, message.MessageType, message.ClientCode, message.MsgCorr);

                    string commandName2 = string.Format("[Load_{0}]", message.MessageType);

                    DataTable dataTable = message.AsDataTable();
                    
                    RunCommand(commandName2, dataTable, msgId, null, null);

                    OnMessageReceived(msgId);

                }
                catch (Exception e)
                {
                    // PluginLogger.LogException(KällTyp.Plugin, "BackOfficeServiceHostSyncPlugin", e, 0);
                    // SimplePlugin.EventLogger_LogException("BackOfficeInbox", e);
                    Trace.TraceError("SyncDestinationService ProcessMessageOld (msgType:" + message.MessageType + ") Error: " + e);
                    throw;
                }
                finally
                {
                    LockObject.Unlock();
                }
            }
            System.Console.WriteLine("done:" + DateTime.Now.ToString());

        }

        public IntMessage GetMessage(long messageId)
        {
            IntMessage result = null;

            using (DbManager db = new DbManager(CreateDataProvider(), destinationConnectionString))
            {
                SqlBuilder sqlBuilder = new SqlBuilder();
                string sql = sqlBuilder.GetMessage(messageId);
                result = db.SetCommand(sql).ExecuteObject<IntMessage>();
            }

            return result;
        }

        public List<IntMessage> GetFailedMessages()
        {
            List<IntMessage> result = null;

            using (DbManager db = new DbManager(CreateDataProvider(), destinationConnectionString))
            {
                SqlBuilder sqlBuilder = new SqlBuilder();
                string sql = sqlBuilder.GetFailedMessages();
                result = db.SetCommand(sql).ExecuteList<IntMessage>();
            }

            return result;
        }

        public List<IntMessageRow> GetFailedMessageRows(long messageId)
        {
            List<IntMessageRow> result = null;

            using (DbManager db = new DbManager(CreateDataProvider(), destinationConnectionString))
            {
                SqlBuilder sqlBuilder = new SqlBuilder();
                string getMsgSql = sqlBuilder.GetMessage(messageId);
                IntMessage msg = db.SetCommand(getMsgSql).ExecuteObject<IntMessage>();

                string sql = sqlBuilder.GetFailedMessageRows(msg);
                result = db.SetCommand(sql).ExecuteList<IntMessageRow>();
            }

            return result;
        }

        #region util



        private int nullCheckInt(int? l)
        {
            if (l.HasValue)
            {
                return l.Value;
            }
            return 0;
        }

        private bool nullCheckBool(bool? l)
        {
            if (l.HasValue)
            {
                return l.Value;
            }
            return false;
        }

        private double nullCheckDouble(double? l)
        {
            if (l.HasValue)
            {
                return l.Value;
            }
            return 0;
        }

        private long nullCheckLong(long? l)
        {
            if (l.HasValue)
            {
                return l.Value;
            }
            return -999;
        }

        private string nullCheckString2(string l, int maxLength)
        {
            if (l == null)
            {
                return "";
            }
            if (l.Length > maxLength)
            {
                return "length error";
            }
            return l;
        }

        private DateTime nullCheckDate(DateTime? d)
        {
            if (d.HasValue)
            {
                return d.Value;
            }
            return new DateTime(1899, 1, 1);
        }

        protected void RunCommand(String sql, DataTable dataTable)
        {
            RunCommand(sql, dataTable, null, null, null);
        }

        protected void RunCommand(String sql, DataTable dataTable, long? msgId, bool? first, bool? last)
        {
            using (SqlConnection connection = new SqlConnection(destinationConnectionString))
            {
                using (SqlCommand command = new SqlCommand(sql, connection))
                {
                    command.CommandType = System.Data.CommandType.StoredProcedure;

                    if (msgId.HasValue)
                    {
                        SqlParameter msgIdParam = new SqlParameter();
                        msgIdParam.ParameterName = "@msgId";
                        msgIdParam.SqlDbType = System.Data.SqlDbType.BigInt;
                        msgIdParam.Value = msgId.Value;
                        command.Parameters.Add(msgIdParam);
                    }

                    if (first.HasValue)
                    {
                        SqlParameter firstParam = new SqlParameter();
                        firstParam.ParameterName = "@firstMessage";
                        firstParam.SqlDbType = System.Data.SqlDbType.Bit;
                        firstParam.Value = first.Value;
                        command.Parameters.Add(firstParam);
                    }

                    if (last.HasValue)
                    {
                        SqlParameter lastParam = new SqlParameter();
                        lastParam.ParameterName = "@lastMessage";
                        lastParam.SqlDbType = System.Data.SqlDbType.Bit;
                        lastParam.Value = last.Value;
                        command.Parameters.Add(lastParam);
                    }

                    if (dataTable != null)
                    {
                        SqlParameter parameter = new SqlParameter();
                        parameter.ParameterName = "@myData";
                        parameter.SqlDbType = System.Data.SqlDbType.Structured;
                        parameter.Value = dataTable;
                        command.Parameters.Add(parameter);

                        string query = "";

                        foreach (System.Data.DataColumn item in dataTable.Columns)
                        {
                            query += item.ColumnName + ", " + item.DataType + "\r\n";
                        }

                        /*string sqlLog = @"C:\sqllog.log";
                        System.IO.File.WriteAllText(sqlLog, query);*/
                    }

                    connection.Open();
                    command.ExecuteNonQuery();
                }
            }
        }

        protected long RunCommand2(String sql, string messageType, string clientCode, Guid guid)
        {
            using (SqlConnection connection = new SqlConnection(destinationConnectionString))
            {
                using (SqlCommand command = new SqlCommand(sql, connection))
                {
                    command.CommandType = System.Data.CommandType.StoredProcedure;
                    command.CommandTimeout = 600;

                    if (messageType != null)
                    {
                        SqlParameter msgIdParam = new SqlParameter();
                        msgIdParam.ParameterName = "@messageType";
                        msgIdParam.SqlDbType = System.Data.SqlDbType.NVarChar;
                        msgIdParam.Size = 40;
                        msgIdParam.Value = messageType;
                        command.Parameters.Add(msgIdParam);
                    }

                    if (clientCode != null)
                    {
                        SqlParameter clientCodeParam = new SqlParameter();
                        clientCodeParam.ParameterName = "@clientCode";
                        clientCodeParam.SqlDbType = System.Data.SqlDbType.NVarChar;
                        clientCodeParam.Size = 20;
                        clientCodeParam.Value = messageType;
                        command.Parameters.Add(clientCodeParam);
                    }

                    if (guid != null)
                    {
                        SqlParameter guidParam = new SqlParameter();
                        guidParam.ParameterName = "@messageGuid";
                        guidParam.SqlDbType = System.Data.SqlDbType.UniqueIdentifier;
                        guidParam.Value = guid;
                        command.Parameters.Add(guidParam);
                    }

                    /*                    if (first.HasValue)
                                        {
                                            SqlParameter firstParam = new SqlParameter();
                                            firstParam.ParameterName = "@firstMessage";
                                            firstParam.SqlDbType = System.Data.SqlDbType.Bit;
                                            firstParam.Value = first.Value;
                                            command.Parameters.Add(firstParam);
                                        }

                                        if (last.HasValue)
                                        {
                                            SqlParameter lastParam = new SqlParameter();
                                            lastParam.ParameterName = "@lastMessage";
                                            lastParam.SqlDbType = System.Data.SqlDbType.Bit;
                                            lastParam.Value = last.Value;
                                            command.Parameters.Add(lastParam);
                                        }
                    */
                    connection.Open();
                    // object o = command.ExecuteScalar();
                    decimal? result = command.ExecuteScalar() as decimal?;
                    if (result.HasValue) return Convert.ToInt64(result.Value);
                    return 0;
                }
            }
        }

        #endregion

        /*public String DeployDestination(long id)
        {
            var rep = GetRepository();
            var entity = rep.FindByIdWithRef(id);

            GhostTableSync sync = GetGhostTableSync(1000);
            sync.CreateDestinationInt(entity);

            entity.DeployDestinationDat = DateTime.Now;
            rep.Update(entity);

            return null;
        }*/

        public Result DeleteOldRowsFromDestinationTable(SyncEntity syncEntity)
        {
            Trace.TraceInformation("SyncDestinationService DeleteOldRowsFromDestinationTable " + syncEntity.Name);

            try
            {
                if(syncEntity.KeepDays > 0 )
                {
                    SqlBuilder sqlBuilder = new SqlBuilder();

                    DbManager.DefaultConfiguration = ""; //to reset possible previous changes            
                    using (DbManager db = new DbManager(CreateDataProvider(), destinationConnectionString))
                    {
                        string deleteOldRows = sqlBuilder.DeleteOldRowsFromDestinationTable(syncEntity);
                        db.SetCommand(deleteOldRows).ExecuteNonQuery();
                    }
                }
            }
            catch(Exception e)
            {
                return Result.UnknownError;
            }

            return Result.Success;
        }

        public bool DeployDestination(SyncEntity syncEntity)
        {
            Trace.TraceInformation("SyncDestinationService DeployDestination " + syncEntity.Name);

            SqlBuilder sqlBuilder = new SqlBuilder();

            try
            {
                DbManager.DefaultConfiguration = ""; //to reset possible previous changes            
                using (DbManager db = new DbManager(CreateDataProvider(), destinationConnectionString))
                {
                    Trace.TraceInformation("SyncDestinationService DeployDestination 1");
                    string dropDropInsertSP = sqlBuilder.DropInsertSP(syncEntity);
                    db.SetCommand(dropDropInsertSP).ExecuteNonQuery();
                    Trace.TraceInformation("SyncDestinationService DeployDestination 2");
                    string dropTableSql = sqlBuilder.DropTable(syncEntity);
                    db.SetCommand(dropTableSql).ExecuteNonQuery();
                    Trace.TraceInformation("SyncDestinationService DeployDestination 3");
                    string dropTypeSql = sqlBuilder.DropType(syncEntity);
                    db.SetCommand(dropTypeSql).ExecuteNonQuery();
                    Trace.TraceInformation("SyncDestinationService DeployDestination 4");
                    string createTableSql = sqlBuilder.CreateTable(syncEntity);
                    db.SetCommand(createTableSql).ExecuteNonQuery();
                    Trace.TraceInformation("SyncDestinationService DeployDestination 5");
                    string sql2 = sqlBuilder.CreateType(syncEntity);
                    db.SetCommand(sql2).ExecuteNonQuery();
                    Trace.TraceInformation("SyncDestinationService DeployDestination 6");
                    string sql1 = sqlBuilder.CreateInsertSP(syncEntity);
                    db.SetCommand(sql1).ExecuteNonQuery();
                    Trace.TraceInformation("SyncDestinationService DeployDestination 7");
                    if(!string.IsNullOrEmpty(syncEntity.TargetTableName))
                    {
                        Trace.TraceInformation("SyncDestinationService DeployDestination 8");
                        try
                        {
                            string createTargetTableSql = sqlBuilder.CreateTargetTable(syncEntity);
                            db.SetCommand(createTargetTableSql).ExecuteNonQuery();
                        }
                        catch(Exception e)
                        {
                            Trace.TraceInformation("SyncDestinationService DeployDestination createTargetTableSql: " + e);
                        }
                        Trace.TraceInformation("SyncDestinationService DeployDestination 9");
                        try
                        {
                            //string dropIntegrationSpSql = sqlBuilder.DropIntegrationSP(syncEntity);
                            //db.SetCommand(dropIntegrationSpSql).ExecuteNonQuery();

                            string createIntegrationSpSql = sqlBuilder.CreateIntegrationSP(syncEntity);
                            Trace.TraceInformation("createIntProc: " + createIntegrationSpSql);
                            db.SetCommand(createIntegrationSpSql).ExecuteNonQuery();
                        }
                        catch(Exception e)
                        {
                            Trace.TraceInformation("SyncDestinationService DeployDestination createIntegrationSpSql: " + e);
                        }
                    }

                    //map.DeployDestinationDat = DateTime.Now;
                }
            }
            catch(Exception e)
            {
                Trace.TraceError("SyncDestinationService DeployDestination Error: " + e);
                return false;
            }

            return true;
        }

        public bool DeployDestEnvironment()
        {
            Trace.TraceInformation("SyncDestinationService DeployDestEnvironment");

            SqlBuilder sqlBuilder = new SqlBuilder();

            try
            {
                DbManager.DefaultConfiguration = ""; //to reset possible previous changes            
                using (DbManager db = new DbManager(CreateDataProvider(), destinationConnectionString))
                {
                    string createMessageTable = sqlBuilder.CreateMessageTable();
                    db.SetCommand(createMessageTable).ExecuteNonQuery();

                    string createLoadMessageSP = sqlBuilder.CreateLoadMessageSP();
                    db.SetCommand(createLoadMessageSP).ExecuteNonQuery();
                }
            }
            catch (Exception e)
            {
                Trace.TraceError("SyncDestinationService DeployDestEnvironment Error: " + e);
                return false;
            }

            return true;
        }

        protected virtual void OnMessageReceived(long msgId) {
            
        }

        public void InitFileTransfer(Guid fileId, long totalBytes, string destPath)
        {
            try
            {
                string key = fileId.ToString();
                Trace.TraceInformation("SyncDestinationService InitFileTransfer id:{0} to {1} {2} Bytes", fileId, destPath, totalBytes);
                SyncChunkFile file = new SyncChunkFile() { FileId = fileId, DestPath = destPath, TotalBytes = totalBytes, Stream = new MemoryStream((int)totalBytes) };
                chunkFiles.Add(key, file);
            }
            catch(Exception e)
            {
                Trace.TraceError("SyncDestinationService InitFileTransfer Error: " + e);
                throw;
            }
        }

        public void SendFilePart(Guid fileId, long offset, long bytes, byte[] data)
        {
            try
            {
                Trace.TraceInformation("SyncDestinationService SendFilePart id:{0} offset:{1} bytes:{2} dictSize:{3}", fileId, offset,bytes, chunkFiles.Count);

                string key = fileId.ToString(); 
                SyncChunkFile file = chunkFiles[key];
                if (file != null)
                {
                    Trace.TraceInformation("SyncDestinationService fileId found");

                    file.Stream.Write(data, 0, (int) bytes);
                    file.CurrBytes += bytes;

                    if (file.CurrBytes == file.TotalBytes)
                    {
                        Trace.TraceInformation("SyncDestinationService SendFilePart file complete");
                        ZipFile zf = null;
                        try
                        {
                            zf = new ZipFile(file.Stream);
                            foreach (ZipEntry zipEntry in zf)
                            {
                                if (!zipEntry.IsFile)
                                {
                                    continue; // Ignore directories
                                }
                                String entryFileName = zipEntry.Name;
                                // to remove the folder from the entry:- entryFileName = Path.GetFileName(entryFileName);
                                // Optionally match entrynames against a selection list here to skip as desired.
                                // The unpacked length is available in the zipEntry.Size property.

                                byte[] buffer = new byte[4096]; // 4K is optimum
                                Stream zipStream = zf.GetInputStream(zipEntry);

                                // Manipulate the output filename here as desired.
                                String fullZipToPath = Path.Combine(file.DestPath, entryFileName);
                                string directoryName = Path.GetDirectoryName(fullZipToPath);
                                if (directoryName.Length > 0)
                                    Directory.CreateDirectory(directoryName);

                                // Unzip file in buffered chunks. This is just as fast as unpacking to a buffer the full size
                                // of the file, but does not waste memory.
                                // The "using" will close the stream even if an exception occurs.
                                using (FileStream streamWriter = System.IO.File.Create(fullZipToPath))
                                {
                                    StreamUtils.Copy(zipStream, streamWriter, buffer);
                                }
                            }
                        }
                        finally
                        {
                            if (zf != null)
                            {
                                zf.IsStreamOwner = true; // Makes close also shut the underlying stream
                                zf.Close(); // Ensure we release resources
                            }
                        }

                        chunkFiles.Remove(key);
                    }
                }
            }
            catch (Exception e)
            {
                Trace.TraceError("SyncDestinationService SendFilePart Error: " + e);
                throw;
            }
        }

    }
}
